home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
DJLSR106.ARJ
/
FILEBUF.CC
< prev
next >
Wrap
C/C++ Source or Header
|
1992-03-30
|
15KB
|
555 lines
// This is part of the iostream library, providing input/output for C++.
// Copyright (C) 1991, 1992 Per Bothner.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#pragma implementation "filebuf.h"
#include "ioprivate.h"
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
// An fstream can be in at most one of put mode, get mode, or putback mode.
// Putback mode is a variant of get mode.
// In a filebuf, there is only one current position, instead of two
// separate get and put pointers. In get mode, the current posistion
// is that of gptr(); in put mode that of pptr().
// The position in the buffer that corresponds to the position
// in external file system is file_ptr().
// This is normally egptr(), except in putback mode, when it is _save_egptr.
// If the field _fb._offset is >= 0, it gives the offset in
// the file as a whole of the start of the buffer (base()).
// PUT MODE:
// If a filebuf is in put mode, pbase() is non-NULL and equal to base().
// Also, epptr() == ebuf().
// Also, eback() == gptr() && gptr() == egptr().
// The un-flushed character are those between pbase() and pptr().
// GET MODE:
// If a filebuf is in get or putback mode, eback() != egptr().
// In get mode, the unread characters are between gptr() and egptr().
// The OS file position corresponds to that of egptr().
// PUTBACK MODE:
// Putback mode is used to remember "excess" characters that have
// been sputbackc'd in a separate putback buffer.
// In putback mode, the get buffer points to the special putback buffer.
// The unread characters are the characters between gptr() and egptr()
// in the putback buffer, as well as the area between save_gptr()
// and save_egptr(), which point into the original reserve buffer.
// (The pointers save_gptr() and save_egptr() are the values
// of gptr() and egptr() at the time putback mode was entered.)
// The OS position corresponds to that of save_egptr().
//
// LINE BUFFERED OUTPUT:
// During line buffered output, pbase()==base() && epptr()==base().
// However, ptr() may be anywhere between base() and ebuf().
// This forces a call to filebuf::overflow(int C) on every put.
// If there is more space in the buffer, and C is not a '\n',
// then C is inserted, and pptr() incremented.
//
// UNBUFFERED STREAMS:
// If a filebuf is unbuffered(), the _shortbuf[1] is used as the buffer.
void filebuf::init()
{
_fb._fake = 0;
_fb._offset = 0;
_fb._fake = 0;
xsetflags(_S_IS_FILEBUF);
_fb._save_gptr = NULL;
_link_in();
_fb._fileno = -1;
}
void streambuf::_un_link()
{
if (_flags & _S_LINKED) {
streambuf **f;
for (f = &_list_all; *f != NULL; f = &(*f)->xchain()) {
if (*f == this) {
*f = xchain();
break;
}
}
_flags &= _S_LINKED;
}
}
void streambuf::_link_in()
{
if (_flags & _S_LINKED) // Already linked.
abort();
_flags |= _S_LINKED;
xchain() = _list_all;
_list_all = this;
}
filebuf::filebuf()
{
init();
}
filebuf::filebuf(int fd)
{
init();
attach(fd);
}
filebuf::filebuf(int fd, char* p, int len)
{
init();
attach(fd);
setbuf(p, len);
}
filebuf::~filebuf()
{
if (!(xflags() & _S_DELETE_DONT_CLOSE))
close();
_un_link();
}
filebuf* filebuf::open(const char *filename, int mode, int prot)
{
if (is_open())
return NULL;
int posix_mode;
int read_write;
if ((mode & (ios::in|ios::out)) == (ios::in|ios::out))
posix_mode = O_RDWR, read_write = _S_CAN_READ+_S_CAN_WRITE;
else if (mode & (ios::out|ios::app))
posix_mode = O_WRONLY, read_write = _S_CAN_WRITE;
else if (mode & (int)ios::in)
posix_mode = O_RDONLY, read_write = _S_CAN_READ;
else
posix_mode = 0, read_write = 0;
if ((mode & (int)ios::trunc) || mode == (int)ios::out)
posix_mode |= O_TRUNC;
if (mode & ios::app)
posix_mode |= O_APPEND;
if (!(mode & (int)ios::nocreate) && mode != ios::in)
posix_mode |= O_CREAT;
if (mode & (int)ios::noreplace)
posix_mode |= O_EXCL;
int fd = ::open(filename, posix_mode, prot);
if (fd < 0)
return NULL;
_fb._fileno = fd;
xsetflags(read_write);
if (mode & ios::ate) {
if (seekoff(0, ios::end) == EOF)
return NULL;
}
return this;
}
filebuf* filebuf::open(const char *filename, const char *mode)
{
if (is_open())
return NULL;
int oflags = 0, omode;
int read_write;
int oprot = 0666;
switch (*mode++) {
case 'r':
omode = O_RDONLY;
read_write = _S_CAN_READ;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT|O_TRUNC;
read_write = _S_CAN_WRITE;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT|O_APPEND;
read_write = _S_CAN_WRITE;
break;
default:
errno = EINVAL;
return NULL;
}
if (mode[0] == '+' || (mode[0] == 'b' && mode[1] == '+')) {
omode = O_RDWR;
read_write = _S_CAN_READ+_S_CAN_WRITE;
}
int fdesc = ::open(filename, omode|oflags, oprot);
if (fdesc < 0)
return NULL;
_fb._fileno = fdesc;
xsetflags(read_write);
return this;
}
filebuf* filebuf::attach(int fd)
{
if (is_open())
return NULL;
_fb._fileno = fd;
xsetflags(_S_CAN_READ|_S_CAN_WRITE|_S_DELETE_DONT_CLOSE);
return this;
}
int filebuf::overflow(int c)
{
if (pptr() == pbase() && c == EOF)
return 0;
if ((xflags() & _S_CAN_WRITE) == 0) // SET ERROR
return EOF;
if (is_reading()) {
if (pptr() != gptr() && pptr() > pbase())
if (do_flush())
return EOF;
setp(gptr(), ebuf());
setg(egptr(), egptr(), egptr());
}
if (allocate() > 0) {
if (xflags() & _S_LINE_BUF) setp(base(), base());
else setp(base(), ebuf());
setg(pbase(), pbase(), pbase());
}
int flush_only;
if (c == EOF) flush_only = 1, c = 0;
else flush_only = 0;
if (epptr() == pbase()) { // Line buffering
if (pptr() < ebuf() && !flush_only) {
xput_char(c);
if (c != '\n')
return (unsigned char)c;
else
flush_only = 1;
}
}
size_t to_do = out_waiting();
if (to_do > 0) {
char *ptr = pbase();
for (;;) {
size_t count = sys_write(ptr, to_do);
if (count == EOF)
return EOF;
_fb._offset += count;
to_do -= count;
if (to_do == 0)
break;
ptr += count;
}
if (xflags() & _S_LINE_BUF) setp(pbase(), pbase());
else setp(pbase(), epptr());
setg(egptr(), egptr(), egptr());
}
if (flush_only)
return c;
else
return sputc(c);
}
int filebuf::underflow()
{
#if 0
/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & __SEOF)
return (EOF);
#endif
if ((xflags() & _S_CAN_READ) == 0) // SET ERROR
return EOF;
retry:
if (gptr() < egptr())
return *(unsigned char*)gptr();
if (_fb._save_gptr) { // Free old putback buffer.
if (eback() != _fb._shortbuf)
FREE_BUF(eback());
_fb._save_gptr = NULL;
setg(base(), _fb._save_gptr, _fb._save_egptr); // Restore get area.
goto retry;
}
allocate();
#if 0
/* if not already reading, have to be reading and writing */
if ((fp->_flags & __SRD) == 0) {
if ((fp->_flags & __SRW) == 0)
return (EOF);
/* switch to reading */
if (fp->_flags & __SWR) {
if (fflush(fp))
return (EOF);
fp->_flags &= ~__SWR;
fp->_w = 0;
fp->_lbfsize = 0;
}
fp->_flags |= __SRD;
} else {
// We were reading. If there is an ungetc buffer,
// we must have been reading from that. Drop it,
// restoring the previous buffer (if any). If there
// is anything in that buffer, return.
if (HASUB(fp)) {
FREEUB(fp);
if ((fp->_r = fp->_ur) != 0) {
fp->_p = fp->_up;
return (0);
}
}
}
#endif
if ((xflags() & _S_LINE_BUF) || unbuffered()) {
// Flush all line buffered files before reading.
streambuf::flush_all_linebuffered();
}
if (pptr() > pbase())
if (do_flush()) return EOF;
int count = sys_read(base(), ebuf() - base());
if (count <= 0) {
if (count == 0)
xsetflags(_S_EOF_SEEN);
else
xsetflags(_S_ERR_SEEN), count = 0;
return EOF;
}
_fb._offset += count;
setg(base(), base(), base() + count);
return *(unsigned char*)gptr();
}
int filebuf::pbackfail(int c)
{
if (pbase() != NULL) { // is writing()
overflow(EOF);
// FIXME: check for writing!
}
if (_fb._save_gptr == NULL) { // No putback buffer.
_fb._save_gptr = gptr(); _fb._save_egptr = egptr();
setg(_fb._shortbuf, _fb._shortbuf+1, _fb._shortbuf+1);
}
else { // Increase size of existing putback buffer.
size_t new_size;
size_t old_size = egptr() - eback();
new_size = eback() == _fb._shortbuf ? 128 : 2 * old_size;
char* new_buf = ALLOC_BUF(new_size);
memcpy(new_buf+(new_size-old_size), eback(), old_size);
if (eback() != _fb._shortbuf)
FREE_BUF(eback());
setg(new_buf, new_buf+(new_size-old_size), new_buf+new_size);
}
gbump(-1);
*gptr() = c;
return (unsigned char)c;
}
int filebuf::do_flush()
{
if (egptr() != pbase()) {
long new_pos = sys_seek(pbase()-egptr(), ios::cur);
if (new_pos == -1)
return EOF;
}
long to_do = pptr()-pbase();
char* ptr = pbase();
while (to_do > 0) {
size_t count = sys_write(ptr, to_do);
if (count == EOF)
return EOF;
if (_fb._offset >= 0)
_fb._offset += count;
to_do -= count;
ptr += count;
}
setg(base(), pptr(), pptr());
setp(base(), base());
return 0;
}
int filebuf::sync()
{
// char* ptr = cur_ptr();
if (pptr() > pbase())
do_flush();
if (gptr() != egptr()) {
if (sys_seek(gptr() - egptr(), ios::cur) == EOF)
return EOF;
}
// FIXME: Handle putback mode!
// setg(base(), ptr, ptr);
return 0;
}
streampos filebuf::seekoff(streamoff offset, _seek_dir dir, int mode)
{
fpos_t result, new_offset, delta;
int count;
// Flush unwritten characters.
// (This may do an unneeded write if we seek within the buffer.
// But to be able to switch to reading, we would need to set
// egptr to ptr. That can't be done in the current design,
// which assumes file_ptr() is egptr. Anyway, since we probably
// end up flushing when we close(), it doesn't make much difference.)
if (pptr() > pbase())
do_flush();
if (unbuffered() || base() == NULL)
goto dumb;
// FIXME: What if buffer already allocated even though unbuffered()
switch (dir) {
case ios::cur:
if (_fb._offset < 0) {
_fb._offset = sys_seek(0, ios::cur);
if (_fb._offset < 0)
return EOF;
}
// Make offset absolute, assuming current pointer is file_ptr().
offset += _fb._offset;
// Now adjust for unread/unflushed characters:
// FIXME - this stuff needs more thought.
#if 0
offset += cur_ptr() - file_ptr();
// FIXME - might be confused if there is a putback buffer.
#else
offset -= egptr() - gptr(); // Subtract unread characters, if any.
if (_fb._save_gptr) // Putback mode
offset -= _fb._save_egptr - _fb._save_gptr;
// Normally, egptr()==pbase(), but if we earlier switched from
// get to put mode, the following will (correctly) decrease offset.
// However, it won't work if there is a putback buffer - FIXME!
offset += pptr() - egptr(); // Add unflushed characters, in put mode.
#endif
dir = ios::beg;
break;
case ios::beg:
break;
case ios::end:
struct stat st;
if (sys_stat(&st) == 0 && (st.st_mode & S_IFMT) == S_IFREG) {
offset += st.st_size;
dir = ios::beg;
}
else
goto dumb;
}
// At this point, dir==ios::beg.
// FIXME: Handle case of there being a putback buffer!
// If destination is within current buffer, optimize:
if (_fb._offset >= 0) {
fpos_t rel_offset = offset - _fb._offset
+ (file_ptr()-base()); // Offset relative to base().
if (rel_offset >= 0 && rel_offset <= egptr() - base()) {
setg(base(), base() + rel_offset, egptr());
setp(base(), base());
return offset;
}
}
// Try to seek to a block boundary, to improve kernal page management.
new_offset = offset & ~(ebuf() - base() - 1);
delta = offset - new_offset;
if (delta > ebuf() - base()) {
new_offset = offset;
delta = 0;
}
result = sys_seek(new_offset, ios::beg);
if (result < 0)
return EOF;
_fb._offset = result;
count = sys_read(base(), ebuf()-base());
if (count < 0)
return EOF;
setg(base(), base(), base()+count);
xflags(xflags() & ~ _S_EOF_SEEN);
return result;
dumb:
if (_fb._save_gptr != NULL) { // Get rid of putback buffer.
if (eback() != _fb._shortbuf)
FREE_BUF(eback());
_fb._save_gptr = NULL;
}
result = sys_seek(offset, dir);
if (result != EOF) {
_flags &= ~_S_EOF_SEEN;
}
char* start = unbuffered() ? _fb._shortbuf : base();
setg(start, start, start);
setp(start, start);
return result;
}
filebuf* filebuf::close()
{
if (!is_open())
return NULL;
// Flush.
overflow(EOF);
/* Free the buffer's storage. */
// Or should that be done only on destruction???
if (_base != NULL && !(_flags & _S_USER_BUF)) {
FREE_BUF(_base);
_base = NULL;
}
int status = sys_close();
_un_link();
_flags = _IO_MAGIC | _S_IS_FILEBUF;
_fb._fileno = EOF;
return status < 0 ? NULL : this;
}
int filebuf::sys_read(char* buf, size_t size)
{
return ::read(_fb._fileno, buf, size);
}
fpos_t filebuf::sys_seek(fpos_t offset, _seek_dir dir)
{
return ::lseek(fd(), offset, (int)dir);
}
long filebuf::sys_write(const void *buf, long n)
{
return ::write(fd(), buf, n);
}
int filebuf::sys_stat(void* st)
{
return ::_fstat(fd(), (struct stat*)st);
}
int filebuf::sys_close()
{
return ::close(fd());
}
int filebuf::sputn(const char *s, int n)
{
// FIXME: OPTIMIZE THIS (specifically, when unbuffered()).
return streambuf::sputn(s, n);
}
int filebuf::sgetn(char *s, int n)
{
// FIXME: OPTIMIZE THIS (specifically, when unbuffered()).
return streambuf::sgetn(s, n);
}